<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title> Gapminder Visualization </title>
    <style>
        body {
            width: 100%;
            height: 100%;
        }

        .leftBox {
            display: flex;
            flex-direction: column;
            width: 60%;
            height: 100%;
            align-items: center;
            text-align: center;
        }

        .tooltip {
            position: absolute;
            text-align: center;
            width: fit-content;
            padding: 10px;
            font: 12px sans-serif;
            font-weight: 800;
            background: white;
            border: 1px solid #333;
            border-radius: 5px;
            pointer-events: none;
            opacity: 0;
        }

        .bubble {
            opacity: 0.7;
            stroke: black;
            stroke-width: 1px;
        }

        .grid line {
            stroke: lightgray;
            stroke-opacity: 0.7;
            shape-rendering: crispEdges;
        }

        .grid path {
            stroke-width: 0;
        }

        #slider-container {
            width: fit-content;
            margin: 20px;
        }

        #play-button {
            margin: 20px;
            padding: 10px 20px;
            font-size: 16px;
        }

        .axis-label {
            font-size: 16px;
            font-weight: bold;
        }

        .hover-line {
            stroke: gray;
            stroke-dasharray: 3, 3;
        }

        .hover-text {
            font-size: 12px;
            font-weight: bold;
        }

        #legend {
            text-align: center;
            display: flex;
            width: 100%;
        }
        #legend div{
            display: flex;
            width: 20%;
            color: white;
            font-weight: 800;
            align-items: center;
            text-align: center;
            justify-content: center;
        }
    </style>
</head>

<body>
    <div class="leftBox">
        <div id="chart"></div>
        <div id="slider-container">
            <input type="range" min="0" max="0" value="0" id="year-slider">
            <button id="play-button">Play</button>
        </div>
        <div class="tooltip" id="tooltip"></div>
        <div id="legend"></div>
    </div>

    <script src="https://d3js.org/d3.v7.js"></script>
    <script type="text/javascript">
        // 获取容器大小
        leftBox = document.querySelector(".leftBox");
        leftBoxStyle = window.getComputedStyle(leftBox);
        leftBoxWidth = parseFloat(leftBoxStyle.width);

        // 设置图表的宽度、高度和边距
        const margin = { top: leftBoxWidth * 0.1, right: leftBoxWidth * 0.025, bottom: leftBoxWidth * 0.05, left: leftBoxWidth * 0.075 },
            width = leftBoxWidth - margin.left - margin.right,
            height = leftBoxWidth * 0.66 - margin.top - margin.bottom;

        // 创建SVG容器
        const svg = d3.select("#chart").append("svg")
            .attr("width", width + margin.left + margin.right)
            .attr("height", height + margin.top + margin.bottom)
            .append("g")
            .attr("transform", `translate(${margin.left},${margin.top})`);

        // 设置x轴和y轴的比例尺
        const x = d3.scaleLog().base(10).range([0, width]);
        const y = d3.scaleLinear().range([height, 0]);
        const radius = d3.scaleSqrt().range([0, 40]);
        const color = d3.scaleOrdinal(d3.schemeCategory10);

        // 设置x轴和y轴
        const xAxis = d3.axisBottom(x).ticks(10, ",");
        const yAxis = d3.axisLeft(y);

        // 创建网格线
        function makeXGridlines() {
            return d3.axisBottom(x).ticks(10);
        }

        function makeYGridlines() {
            return d3.axisLeft(y).ticks(10);
        }

        // 创建提示框
        const tooltip = d3.select("#tooltip");

        // 添加背景年份文本
        const yearText = svg.append("text")
            .attr("class", "year-text")
            .attr("x", width / 2)
            .attr("y", height / 1.5)
            .attr("text-anchor", "middle")
            .attr("font-size", leftBoxWidth * 0.23 + "px")
            .attr("fill", "lightgray")
            .attr("opacity", 0.5)
            .text("0");

        // 添加虚线和文字元素
        const hoverLineX = svg.append("line")
            .attr("class", "hover-line")
            .attr("y1", 0)
            .attr("y2", height)
            .style("opacity", 0);

        const hoverLineY = svg.append("line")
            .attr("class", "hover-line")
            .attr("x1", 0)
            .attr("x2", width)
            .style("opacity", 0);

        const hoverTextX = svg.append("text")
            .attr("class", "hover-text")
            .attr("text-anchor", "middle")
            .style("opacity", 0);

        const hoverTextY = svg.append("text")
            .attr("class", "hover-text")
            .attr("text-anchor", "middle")
            .style("opacity", 0);

        // 读取CSV数据
        d3.csv("../data/newData.csv").then(data => {
            // 数据处理：将字符串转换为数值类型
            data.forEach(d => {
                d.year = +d.year;
                d.income = +d.income;
                d.lifeExpectancy = +d.lifeExpectancy;
                d.population = +d.population;
            });

            // 按年份组织数据
            const nestedData = d3.groups(data, d => d.year);
            const years = nestedData.map(d => d[0]);
            const allCountries = data;

            // 设置比例尺的域
            x.domain(d3.extent(allCountries, d => d.income));
            y.domain(d3.extent(allCountries, d => d.lifeExpectancy));
            radius.domain(d3.extent(allCountries, d => d.population));

            // 添加网格线到SVG容器
            svg.append("g")
                .attr("class", "grid")
                .attr("transform", `translate(0,${height})`)
                .call(makeXGridlines()
                    .tickSize(-height)
                    .tickFormat(""));

            svg.append("g")
                .attr("class", "grid")
                .call(makeYGridlines()
                    .tickSize(-width)
                    .tickFormat(""));

            // 添加x轴和y轴到SVG容器
            svg.append("g")
                .attr("class", "x axis")
                .attr("transform", `translate(0,${height})`)
                .call(xAxis);

            svg.append("text")
                .attr("class", "axis-label")
                .attr("x", width / 1.75)
                .attr("y", height * 1.06)
                .style("text-anchor", "end")
                .text("Income (log scale)");

            svg.append("g")
                .attr("class", "y axis")
                .call(yAxis)

            svg.append("text")
                .attr("class", "axis-label")
                .attr("transform", "rotate(-90)")
                .attr("x", -height / 2 * 0.8)
                .attr("y", -width * 0.04)
                .attr("dy", ".71em")
                .style("text-anchor", "end")
                .text("Life Expectancy");

            // 当鼠标进入气泡时，显示具体信息
            function bubbleMouseenterEvent(event, d) {
                tooltip.transition()
                    .duration(200)
                    .style("opacity", 0.9);

                let thePop = d.population;
                if (d.population > 1e9)
                    thePop = parseFloat(d.population / 1e9) + "B";
                else if (d.population > 1e6)
                    thePop = parseFloat(d.population / 1e6) + "M";
                else if (d.population > 1e3)
                    thePop = parseFloat(d.population / 1e3) + "k";

                tooltip.html(`${d.name}<br>Population: ${thePop}`)
                    .style("left", (event.pageX + 10) + "px")
                    .style("top", (event.pageY - 28) + "px");

                const cx = x(d.income);
                const cy = y(d.lifeExpectancy);

                hoverLineX.attr("x1", cx)
                    .attr("x2", cx)
                    .style("opacity", 1);

                hoverLineY.attr("y1", cy)
                    .attr("y2", cy)
                    .style("opacity", 1);

                hoverTextX.attr("x", cx)
                    .attr("y", height + 15)
                    .text(d.income)
                    .style("opacity", 1);

                hoverTextY.attr("x", -20)
                    .attr("y", cy)
                    .text(d.lifeExpectancy)
                    .style("opacity", 1);
            }

            // 创建一个函数来更新气泡图
            function update(year) {
                // 查找 year 相同的数据
                const currentYearData = nestedData.find(d => d[0] === year)[1];

                // 更新所有气泡
                const circles = svg.selectAll(".bubble")
                    .data(currentYearData, d => d.name);

                // 设置属性
                circles.enter().append("circle")
                    .attr("class", "bubble")
                    .attr("cx", d => x(d.income))
                    .attr("cy", d => y(d.lifeExpectancy))
                    .attr("r", d => radius(d.population))
                    .style("fill", d => color(d.region))
                    // 显示详细信息
                    .on("mouseenter", function (event, d) {
                        bubbleMouseenterEvent(event, d);
                    })
                    // 清除信息
                    .on("mouseleave", function (d) {
                        tooltip.transition()
                            .duration(500)
                            .style("opacity", 0);
                        hoverLineX.style("opacity", 0);
                        hoverLineY.style("opacity", 0);
                        hoverTextX.style("opacity", 0);
                        hoverTextY.style("opacity", 0);
                    })
                    .on("click", function (event, d) {
                        // 防止事件冒泡到SVG容器
                        event.stopPropagation();
                        const selectedRegion = d.region;
                        svg.selectAll(".bubble")
                            .filter(d => d.region === selectedRegion)
                            .style("opacity", 0.7)
                            .on("mouseenter", function (event, d) {
                                bubbleMouseenterEvent(event, d);
                            })
                            .raise();
                        svg.selectAll(".bubble")
                            .filter(d => d.region != selectedRegion)
                            .style("opacity", 0.1)
                            // 清除多余的效果 不显示详细信息
                            .on("mouseenter", function () { })
                    })
                    .merge(circles)
                    .transition().duration(500)
                    .attr("cx", d => x(d.income))
                    .attr("cy", d => y(d.lifeExpectancy))
                    .attr("r", d => radius(d.population ** 1.05))
                    .style("fill", d => color(d.region));

                circles.exit().remove();

                // 更新背景年份文本
                yearText.text(year);
            }

            // 添加一个透明矩形覆盖整个SVG容器
            svg.append("rect")
                .attr("width", width)
                .attr("height", height)
                .style("fill", "none")
                .style("pointer-events", "all")
                .on("click", function () {
                    svg.selectAll(".bubble")
                        .style("opacity", 0.7)
                        .on("mouseenter", function (event, d) {
                            bubbleMouseenterEvent(event, d);
                        });
                });

            // 初始年份的气泡图
            let currentYearIndex = 0;
            update(years[currentYearIndex]);

            // 设置滑动条的属性
            const slider = document.getElementById("year-slider");
            slider.max = years.length - 1;
            slider.value = currentYearIndex;

            // 调制滑动条样式
            slider.style.width = width + "px";

            // 滑动条事件监听器
            slider.addEventListener("input", function () {
                currentYearIndex = +this.value;
                update(years[currentYearIndex]);
            });

            // 自动播放功能
            let playInterval;
            const playButton = document.getElementById("play-button");
            playButton.addEventListener("click", function () {
                if (playInterval) {
                    clearInterval(playInterval);
                    playInterval = null;
                    playButton.textContent = "Play";
                } else {
                    if (currentYearIndex === years.length - 1)
                        return;
                    playButton.textContent = "Pause";
                    playInterval = setInterval(() => {
                        currentYearIndex = (currentYearIndex + 1);
                        slider.value = currentYearIndex;
                        update(years[currentYearIndex]);
                        if (currentYearIndex === years.length - 1)
                            playButton.click();
                    }, 200);
                }
            });

            // 创建图例
            function createLegend(data) {
                const regions = Array.from(new Set(data.map(d => d.region)));
                const legend = d3.select("#legend");

                legend.selectAll("div")
                    .data(regions)
                    .enter()
                    .append("div")
                    .style("background-color", d => color(d))
                    .style("padding", "5px")
                    .style("margin", "2px")
                    .style("cursor", "pointer")
                    .text(d => d)
                    .on("click", function(event, d) {
                        svg.selectAll(".bubble")
                            .filter(e => e.region === d)
                            .style("opacity", 0.7)
                            .on("mouseenter", function (event, e) {
                                bubbleMouseenterEvent(event, e);
                            })
                            .raise();
                        svg.selectAll(".bubble")
                            .filter(e => e.region != d)
                            .style("opacity", 0.1)
                            // 清除多余的效果 不显示详细信息
                            .on("mouseenter", function () {});
                    });
            }
            createLegend(data);
        });
    </script>
</body>

</html>
